1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
---
import { getCollection, render } from 'astro:content';
import MarketingLayout from '../../components/MarketingLayout.astro';
export async function getStaticPaths() {
const releases = await getCollection('releases');
return releases
.filter((release) => !release.data.draft)
.map((release) => {
const slug = release.data.path;
return {
params: { slug },
props: { release, slug },
};
});
}
const { release, slug } = Astro.props;
const { Content, headings } = await render(release);
const sectionHeadings = headings.filter((heading) => heading.depth === 2);
---
<MarketingLayout
title={`${release.data.version}: ${release.data.title} — Herdr releases`}
description={release.data.description}
canonicalPath={`/releases/${slug}/`}
ogType="article"
>
<main class="content-shell">
<div class="article-layout release-layout section">
<aside class="article-toc" aria-label="Release sections">
<p>Sections</p>
<nav>
{sectionHeadings.map((heading) => (
<a href={`#${heading.slug}`} data-heading-link={heading.slug}>{heading.text}</a>
))}
</nav>
</aside>
<article class="article-shell release-shell">
<header class="article-header release-header">
<p class="post-meta">
<a href="/releases/">Releases</a>
<span>{release.data.version}</span>
<time datetime={release.data.date.toISOString()}>{release.data.date.toLocaleDateString('en', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}</time>
</p>
<h1>{release.data.title}</h1>
<p>{release.data.description}</p>
</header>
<div class="article-prose release-prose">
<Content />
</div>
</article>
</div>
</main>
<script>
const headings = Array.from(document.querySelectorAll('.article-prose h2[id]'));
const links = new Map(
Array.from(document.querySelectorAll('[data-heading-link]')).map((link) => [
link.getAttribute('data-heading-link'),
link,
]),
);
function setActive(id) {
for (const link of links.values()) link.removeAttribute('aria-current');
links.get(id)?.setAttribute('aria-current', 'true');
}
if (headings.length > 0 && links.size > 0) {
setActive(headings[0].id);
const observer = new IntersectionObserver(
(entries) => {
const visible = entries
.filter((entry) => entry.isIntersecting)
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)[0];
if (visible?.target instanceof HTMLElement) setActive(visible.target.id);
},
{ rootMargin: '-18% 0px -68% 0px', threshold: 0 },
);
for (const heading of headings) observer.observe(heading);
}
</script>
</MarketingLayout>
|